package org.flexthinker.module.impl
{
    import mx.events.ModuleEvent;

    import org.flexthinker.module.Descriptor;
    import org.flexthinker.module.DescriptorLoader;
    import org.flexthinker.module.DescriptorLoaderFactory;

    import flash.events.Event;

    import flash.system.ApplicationDomain;

    import flash.system.SecurityDomain;

    import flash.utils.ByteArray;

    import mx.core.IFlexModuleFactory;
    import mx.core.UIComponent;
    import mx.logging.ILogger;
    import mx.logging.Log;

    import mx.utils.StringUtil;

    import spark.components.SkinnableContainer;

    /**
     * Loader for ModuleDescriptor.
     * Loads a module SWF and its dependencies and reports the overall progress.
     */
    public class ModuleDescriptorLoader extends CompositeDescriptorLoader
    {
        private var log:ILogger = Log.getLogger("org.flexthinker.module.impl.ModuleDescriptorLoader");

        private var numModuleLoadersCompleted:int = 0;

        private var moduleDescriptor:ModuleDescriptor;

        private var descriptors:Vector.<Descriptor>;

        private var _moduleInstance:UIComponent;

        /**
         * DescriptorImpl used to preload the current module into the cache
         * so that ModuleManager uses it form cache to create it.
         */
        private var _moduleDescriptorImpl:DescriptorImpl;

        /**
         * Constructor.
         * @param descriptor
         *            Descriptor for the module.
         * @param loaderFactory
         *             Factory is used to create loaders for dependencies.
         */
        public function ModuleDescriptorLoader(descriptor:Descriptor, loaderFactory:DescriptorLoaderFactory = null)
        {
            super(descriptor, loaderFactory);
            initDescriptors();
        }


        override public function load(applicationDomain:ApplicationDomain = null, securityDomain:SecurityDomain = null,
                bytes:ByteArray = null, moduleFactory:IFlexModuleFactory = null):void
        {
            initDescriptors();
            super.load(applicationDomain, securityDomain, bytes, moduleFactory);
        }

        /**
         * Call this method after <code>ModuleEvent.READY</code> event,
         * in order to add the module and its dependencies(skins, resource bundles )
         * into the <code>targetObject</code> component.
         * @param targetObject
         *          The Visual Component where the module should be added
         */
        override public function init(targetObject:UIComponent):void
        {
            super.init(targetObject);

            _moduleInstance = initModuleItself(targetObject);

            resetDescriptorsToModuleDependencies();

            if (numDescriptors == 0)
            {
                complete();
            }
            else
            {
                initDependencies();
            }
        }

        // ------------
        //  internals
        // ------------

        override protected function getDescriptors():Vector.<Descriptor>
        {
            return descriptors;
        }


        override protected function moduleReady(event:ModuleEvent):void
        {
            super.moduleReady(event);
        }

        /**
         * When this method is called, the module and its dependencies are all ready,
         * and they have been also added to the UI.
         */
        protected function complete():void
        {
            dispatchEvent(new Event(Event.COMPLETE));
        }

        protected function getModuleInstance():UIComponent
        {
            return _moduleInstance;
        }


        /**
         * This method creates a single-leveled Vector with descriptors to be loaded as CompositeDescriptorLoader is
         * specialized to do that. That's why everything is loaded initially as single level descriptors.
         *
         * This means that the moduleDescriptor is converted into a DescriptorImpl and gets loaded as any other dependency.
         * When init() method is called, the list of descriptors is set bask to the original.
         * @see ModuleDescriptorLoader#resetDescriptorsToModuleDependencies()
         */
        private function initDescriptors():void
        {
            moduleDescriptor = ModuleDescriptor(descriptor);
            descriptors = new Vector.<Descriptor>();

            var moduleDescr:DescriptorImpl = getDescriptorImplFromCurrentDescriptor();
            descriptors.push(moduleDescr);
            descriptors = descriptors.concat(moduleDescriptor.dependencies);
        }

        /**
         * This method returns a basic DescriptorImpl for the current Module.
         * DescriptorImpl is used to preload the module in the cache,
         * at the same time with the other children descriptors it contains.
         * The reason for doing this is to have an unified progress.
         *
         * After the module is loaded, the loaded bytes are then used by Flex SDK to instantiate
         * the loaded module without re-loading it again.
         * @return
         *      DescriptorImpl instance with the current module's descriptor
         */
        private function getDescriptorImplFromCurrentDescriptor():DescriptorImpl
        {
            if (!_moduleDescriptorImpl)
            {
                _moduleDescriptorImpl = new DescriptorImpl(descriptor.url);
                _moduleDescriptorImpl.applicationDomain = descriptor.applicationDomain;
                _moduleDescriptorImpl.securityDomain = descriptor.securityDomain;
            }

            return _moduleDescriptorImpl;
        }

        private function resetDescriptorsToModuleDependencies():void
        {
            descriptors = moduleDescriptor.dependencies;
        }

        private function initModuleItself(targetObject:UIComponent):UIComponent
        {
            var newModule:UIComponent = UIComponent(factory.create());

            CONFIG::DEBUG
            {
                log.debug("Initializing {0} ( ready={1} ) ... in  {2}, targetUIComponent={3} ", this, this.ready, this,
                        newModule);
            }

            if (targetObject is SkinnableContainer)
            {
                SkinnableContainer(targetObject).addElement(newModule);
            }
            else
            {
                targetObject.addChild(newModule);
            }

            return newModule;
        }


        override protected function moduleUnloaded():void
        {
            super.moduleUnloaded();

            removeFromView();
        }

        private function removeFromView():void
        {
            var inst:UIComponent = getModuleInstance();
            if (inst && inst.parent && inst.parent.contains(inst))
            {
                try
                {
                    inst.parent.removeChild(inst);
                }
                catch(err:Error)
                {
                    inst.parent["removeElement"](inst);
                }
                /*
                 if (inst.parent is SkinnableContainer)
                 {
                 SkinnableContainer(inst.parent).removeElement(inst);
                 }
                 else
                 {
                 inst.parent.removeChild(inst);
                 }
                 */

                _moduleInstance = null;
            }
            else
            {
                CONFIG::DEBUG
                {
                    log.debug("Could not remove {0} ( ready={1} ) ... from  {2}, targetUIComponent={3} ", this,
                            this.ready, this, inst ? inst.parent : 'parent N/A');
                }
            }
        }

        private function initDependencies():void
        {
            for each (var loader:DescriptorLoader in getDescriptorLoaders())
            {
                CONFIG::DEBUG
                {
                    log.debug("Initializing {0} ( ready={1} ) ... in  {2}, targetUIComponent={3} ", loader,
                            loader.ready, this,
                            _moduleInstance);
                }
                loader.init(_moduleInstance);
                loader.addEventListener(Event.COMPLETE, loader_completeHandler);
            }
        }

        private function loader_completeHandler(event:Event):void
        {
            var loader:DescriptorLoader = DescriptorLoader(event.currentTarget);
            loader.removeEventListener(Event.COMPLETE, loader_completeHandler);

            numModuleLoadersCompleted++;
            CONFIG::DEBUG
            {
                log.debug(StringUtil.substitute("{0} out of {1} descriptors Completed", numModuleLoadersCompleted,
                        numDescriptors));
            }
            if (numModuleLoadersCompleted == numDescriptors)
            {
                complete();
            }
        }

    }
}